local Util = require("common.Util")
local ObjAoe = require("core.ObjAoe")
local ObjTrap = require("core.ObjTrap")
local ObjMagic = require("core.ObjMagic")
local ObjMonster = require("core.ObjMonster")
local Obj = require("core.Obj")
local ObjHuman = require("core.ObjHuman")
local Timer = require("core.Timer")
local Msg = require("core.Msg")
local Map = require("core.Map")
local MonsterExcel = require("excel.Monster").monster
local SkillExcel = require("excel.Skill").Skill
local UtilityExcel= require("excel.Skill").Utility
local RoleDefine = require("role.RoleDefine")
local Skill = require("skill.Skill")
local Notify = require("skill.Notify")
local SkillDefine = require("skill.Define")
local SkillNotify = require("skill.Notify")
local Status = require("scene.Status")
local SceneEvent = require("core.SceneEvent")
local CombatCalc = require("skill.CombatCalc")
local Callback = require("skill.Callback")
local Cmds = require("buffer.Cmds")
local Buffer = require("buffer.Buffer")
local ExcelBuffer = require("excel.Buffer")
local HorseLogic = require("horse.HorseLogic")
local MonsterAi = require("monster.MonsterAi")
local CopyDefine = require("copy.Define")
local CopyL = require("copy.CopyL")
local RoleRevive = require("role.RoleRevive")
local ExcelMapConf = require("excel.Map")


-- ******************************** 注：主线程处理子线程 作用回调 ***************************************
local UTILITY_TYPE_MIAN   = SkillDefine.UTILITY_TYPE_MIAN

local random			= math.random
local hurt        = CombatCalc.hurt



local data = {}

function handle(obj, target, unit)
    local utilityConfig = unit.utilityConfig
    local utility   = utilityConfig.utility
    local callback    = _ENV[utility]
    local utilityArgs = unit.utilityArgs
    local argsNum   = #utilityArgs

    if utilityConfig.move_canel == 1 and obj.cg_move_time and obj.cg_move_time > unit.createTime then
        return --移动取消作用
    end
    local isQuake
    if target then
        local targetType    = target.obj_type
        if (utilityConfig.hit_back_fly_target == 0 and (targetType ~= Obj.TYPE_MONSTER or ObjMonster.getValueByMonsterType(target, "can_hit_back_fly")==1)) or
           (utilityConfig.hit_back_fly_target == 1 and targetType == Obj.TYPE_HUMAN) or
           (utilityConfig.hit_back_fly_target == 2 and targetType == Obj.TYPE_MONSTER and ObjMonster.getValueByMonsterType(target, "can_hit_back_fly")==1) then
            if random(1, 100) <= utilityConfig.hitFlyPer then
                do_quake(obj, SkillDefine.QUAKE_FLY, target, unit)
                if targetType == Obj.TYPE_MONSTER then
                    target.wake_up_time = Timer.now + 1000
                end
                isQuake = true
            elseif random(1, 100) <= utilityConfig.hitBackPer then
                do_quake(obj, SkillDefine.QUAKE_BACK, target, unit)
                if targetType == Obj.TYPE_MONSTER then
                    target.wake_up_time = Timer.now + 1000
                end
                isQuake = true
            elseif random(1, 100) <= utilityConfig.hitJumpPer then  --弹起
                do_quake(obj, SkillDefine.QUAKE_JUMP, target, unit)
                if targetType == Obj.TYPE_MONSTER then
                    target.wake_up_time = Timer.now + 1000
                end
                isQuake = true
            elseif random(1, 100) <= utilityConfig.hitFloatPer then --浮空
                do_quake(obj, SkillDefine.QUAKE_FLOAT, target, unit)
                if targetType == Obj.TYPE_MONSTER then
                    target.wake_up_time = Timer.now + 2000
                end
                isQuake = true
            end
        end
    end

    if utility=="" then return end
    data.isHit, data.isHeavyHit, data.isGedang, data.shangHai,  data.isAtk, data.noDie, data.isGiddy, data.isLuoma = true, false, false, nil, nil, nil, nil, nil

    if callback then
        if argsNum<4 then -- 处理不同行参个数情况
            callback(obj, target, unit, utilityArgs[1], utilityArgs[2], utilityArgs[3])
        elseif argsNum<8 then
            callback(obj, target, unit, utilityArgs[1], utilityArgs[2], utilityArgs[3], utilityArgs[4], utilityArgs[5], utilityArgs[6], utilityArgs[7])
        else
            callback(obj, target, unit, utilityArgs[1], utilityArgs[2], utilityArgs[3], utilityArgs[4], utilityArgs[5], utilityArgs[6], utilityArgs[7], utilityArgs[8], utilityArgs[9], utilityArgs[10], utilityArgs[11], utilityArgs[12])
        end
    end

    if data.isAtk then
        if target then
            if data.isHit  then
                Notify.hitQuake(obj, target, utilityConfig)
            end
            Callback.onAttack(obj, target, unit.skillID, utilityConfig, data.isHit, data.isHeavyHit, data.isGedang, data.shangHai, data.noDie, data.isGiddy, data.isLuoma, isQuake)
        end
    elseif data.isHit then
        if utilityConfig.hitID~=0 and unit.utilityIndex==UTILITY_TYPE_MIAN then -- 主作用
            Notify.GC_SKILL_HIT2(obj, unit.utilityID, target and target.id or obj.id)
        end
    end
end


function do_quake(sender, quakeType, target, unit)
    if not target or Obj.getHp(target) <1 then return end -- 被攻击的人不存在
    local utilityConfig   = UtilityExcel[unit.utilityID]
    local r     = utilityConfig.repelLen

    if quakeType == SkillDefine.QUAKE_FLY then
        local sceneID   = target.scene_id
        SceneEvent.setBan(sceneID)

        local isBlock = Map.through(sceneID, unit.x, unit.y, target.x, target.y) ~= target.x
        local tx, ty
        tx, ty = Util.getTargetPoint(unit.x, unit.y, target.x, target.y, -r)
        if isBlock then
            tx, ty = Map.through(sceneID, target.x, target.y, tx, ty)
        else
            tx, ty = Map.through(sceneID, unit.x, unit.y, tx, ty)
        end

        Obj.jump(target, tx, ty, Msg.gc.GC_SKILL_HIT_FLY)

        Buffer.delStop(target, Buffer.STOP_TYPE_MOVE)
    elseif quakeType == SkillDefine.QUAKE_BACK then
        local sceneID   = target.scene_id
        SceneEvent.setBan(sceneID)

        local isBlock = Map.through(sceneID, unit.x, unit.y, target.x, target.y) ~= target.x
        local tx, ty
        tx, ty = Util.getTargetPoint(unit.x, unit.y, target.x, target.y, -r)
        if isBlock then
            tx, ty = Map.through(sceneID, target.x, target.y, tx, ty)
        else
            tx, ty = Map.through(sceneID, unit.x, unit.y, tx, ty)
        end

        Obj.jump(target, tx, ty, Msg.gc.GC_SKILL_HIT_BACK)
        Buffer.delStop(target, Buffer.STOP_TYPE_MOVE)

    elseif quakeType == SkillDefine.QUAKE_JUMP then
        local mm = Msg.gc.GC_SKILL_HIT_UP
        mm.obj_id = target.id
        mm.x    = target.x
        mm.y    = target.y
        Obj.sendArea(mm, target.scene_id, target.x, target.y)
    elseif quakeType == SkillDefine.QUAKE_FLOAT then
        local mm = Msg.gc.GC_SKILL_HIT_FLOAT
        mm.obj_id = target.id
        mm.x = target.x
        mm.y = target.y
        mm.time = 2000
        Obj.sendArea(mm, target.scene_id, target.x, target.y)
    end

    Obj.resetPathParam(target)
end



function clearSkillCooldown(obj)
    local userData = Skill.getUserData(obj)
    local coolDownList = userData.cooldown
    local flag
    for i = 0, userData.cooldownLen - 1 do
        local skill = SkillExcel[coolDownList[i].id]
        if skill then            
            coolDownList[i].time = Timer.now - skill.coolDown
            coolDownList[i].count = skill.coolDownCount
            flag = true
        end
    end
    if flag and obj.fd then
        local mm = Msg.gc.GC_SKILL_COOLDOWN
        mm.skillGroupID = 0
        mm.type = SkillDefine.COOLDOWN_SKILL_ALL_DEC
        mm.time = 5000
        mm.rate = 1
        Msg.send(mm, obj.fd)
    end
end


function onAddSub(owner, skillID, utilityID, maxNum, child)
    if (owner.obj_type ~= Obj.TYPE_HUMAN and owner.obj_type ~= Obj.TYPE_MONSTER) or maxNum<1 then return end
    local userData = Skill.getUserData(owner)
    local subList = userData.subList
    subList[skillID] = subList[skillID] or {}
    local list = subList[skillID]
    list[utilityID] = list[utilityID] or {}

    local data = list[utilityID]

    local first
    local num = 0
    local getObj = Obj.getObj
    for id, uid in pairs(data) do
        local obj = getObj(id, uid)
        if obj then
            num = num + 1
            if not first or first.addSubTime>obj.addSubTime then
                first = obj
            end
        else
            data[id] = nil
        end
    end
    if num>=maxNum then
        Obj.destroyAllType(first)
        data[first.id] = nil
    end

    data[child.id] = child.uid
    child.addSubTime = Timer.now
    if child.owner then child.owner.utilityID = utilityID end
    if child.owner then child.owner.skillID = skillID end
end

--obj 给target加血
function sendAddHp(obj, target, hp)
    local currHp = Obj.getHp(target)
    if currHp<1 then return end

    --  hp = passive.hpRate(obj, hp)
    if hp<1 then return end
    Obj.setHp(target, currHp+hp)

    Notify.GC_SKILL_HIT(target, obj.id, -hp)
end

function sendAddMp(target, mp)
    local currHp = Obj.getHp(target)
    if currHp<1 then return end

    -- hp = passive.hpRate(obj, hp)
    if mp<1 then return end
    Obj.setMp(target, currHp+mp)
    if target.obj_type == Obj.TYPE_HUMAN then
        ObjHuman.sendAreaHpMp(target)
    end
end


-- ********************************** [ 作用函数 ] *******************************************
-- ************************************* 位移类 ***********************************************

-- [瞬移]
function jump(obj, target, unit)--, maxLen, fixed)
    Obj.jump(obj, unit.tx, unit.ty, Msg.gc.GC_JUMP)
end

-- [跳]
function jumpTo(obj, target, unit)
    Obj.jump(obj, unit.tx, unit.ty)
end

-- [冲锋]
function rush(obj, target, unit, canRushBlock, fixedLen, isPush)
    isPush = isPush==1
    if isPush and target and target.monster_id  and ObjMonster.getValueByMonsterType(target, "can_hit_back_fly")  == 0  then
        isPush = false --怪物不能被推
    elseif isPush and target and obj.obj_type == Obj.TYPE_HUMAN and target.obj_type == Obj.TYPE_HUMAN and target.db.lv >obj.db.lv then
        isPush = false --PVP时，不能推等级比自己高的人
    end
    local userData = Skill.getUserData(obj)
    if isPush and target and userData[1] then  -- 是否推人
        Obj.jump(target, unit.tx, unit.ty)
        Status.onMove(target)
        Obj.jump(obj, userData[1], userData[2])        
        if target.monster_id then
            target.wake_up_time = 1000 + Timer.now
        end
    else
        Obj.jump(obj, unit.tx, unit.ty)
    end
end

--答题踢人
function onQAKick(obj, target)--, unit)
   if obj.obj_type ~= Obj.TYPE_HUMAN then
      return
   end
   if CopyL.isCopyBySceneID(obj.scene_id) ~= CopyDefine.COPY_DATI_ID then 
      return
   end
   if Cmds.passive.immunityMove(target) then   --免疫
      return
   end
   local map_id = Map.scene_id_to_map_id[obj.scene_id]
   local tx,ty
   if Map.is_in_area_id(map_id, target.x, target.y, CopyDefine.DATI_AREA_1) then
        tx, ty = Map.getPositionInArea(obj.scene_id, CopyDefine.DATI_AREA_2)       
   elseif Map.is_in_area_id(map_id, target.x, target.y, CopyDefine.DATI_AREA_2) then
        tx, ty= Map.getPositionInArea(obj.scene_id, CopyDefine.DATI_AREA_1)
   else
        if obj.x < target.x then
            tx, ty = Map.getPositionInArea(obj.scene_id, CopyDefine.DATI_AREA_2)   
        else
            tx, ty= Map.getPositionInArea(obj.scene_id, CopyDefine.DATI_AREA_1)
        end         
   end   
   Obj.jump(target, tx, ty, Msg.gc.GC_NPC_JUMP) 
end
--移形换位
function exchangePos(obj, target)--, unit)

   if Cmds.passive.immunityMove(target) then   --免疫
      return
   end
   local sx,sy = obj.x,obj.y
   Obj.jump(obj, target.x, target.y, Msg.gc.GC_JUMP)
   Obj.jump(target, sx, sy,  Msg.gc.GC_JUMP)
end

-- ************************************* ATK类 ***********************************************

function atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, noDef)
    local noBaojiShanhai
    local skillConfig = SkillExcel[unit.skillID]
    data.isHit, data.isHeavyHit, data.isGedang, data.shangHai, noBaojiShanhai, data.isGiddy, data.isLuoma = hurt(obj, target, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, nil, noDef, skillConfig and skillConfig.isNormal)
    data.isAtk = true
end

--[ 造成HP伤害 ]      伤害百分比，绝对值加成，额外暴击几率，额外命中几率 主属性类型，主属性加成比例
function atkNoDie(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    data.noDie = true
end

-- [ 造成HP伤害（衰减）]        衰减百分比, 伤害百分比，绝对值加成，额外暴击几率，额外命中几率 主属性类型，主属性加成比例
function atkAttenuate(obj, target, unit, attenuateRate, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    if unit.flag then
        skillHurt = attenuateRate * skillHurt
    end
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
end

--[ 造成HP伤害并计数 ] 调用特定技能  伤害百分比，绝对值加成，额外暴击几率，额外命中几率，计数次数，达成后调用技能
function countAtk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, count, callSkillID)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    local userData = Skill.getUserData(obj)
    local countAtk = userData.countAtk
    local v = countAtk[unit.skillID]
    if not v then
        v = {count=1}
        countAtk[unit.skillID] = v
    end

    v.skillID = callSkillID
    v.lastTime = Timer.now
    v.maxCount = count
    v.count = v.count + 1
end

-- [恢复血量]
function addHp(obj, target, unit, atkRate, value, rate, mianAttr, mainAttrRate) --攻击比例，绝对值加成，直接百分比回复，主属性类型，主属性加成比例
    local objAttr = obj.attr
    local hp

    if mianAttr and mianAttr~=0 then
        hp = ( objAttr[RoleDefine.ATK] * atkRate ) + value + ( objAttr[mianAttr] * mainAttrRate ) + rate * Obj.getHpMax(target)
    else
        hp = ( objAttr[RoleDefine.ATK] * atkRate ) + value + rate * Obj.getHpMax(target)
    end

    sendAddHp(obj, target, hp)
end

-- [恢复气力]
function addMp(obj, target, unit, atkRate, value, rate, mianAttr, mainAttrRate) --攻击比例，绝对值加成，直接百分比回复，主属性类型，主属性加成比例
    if target.obj_type ~= Obj.TYPE_HUMAN then return end
    local objAttr = obj.attr
    local mp
    if mianAttr~=0 then
        mp =( objAttr[RoleDefine.ATK] * atkRate )+ value + ( objAttr[mianAttr] * mainAttrRate ) + rate * Obj.getMpMax(target)
    else
        mp =( objAttr[RoleDefine.ATK] * atkRate )+ value + rate * Obj.getMpMax(target)
    end

    sendAddMp(target, mp)

    data.isHit = HIT_TYPE_OTHER
end

-- [攻击并加BUF]
function atkAddBuff(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, bufID)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    if data.shangHai then
        Buffer.add(bufID, target, obj, unit.level)
    end
end

-- [攻击并加多个BUF]
function atkAddMultiBuff(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, buffID1, buffID2, buffID3)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    if data.isHit then
        addMultiBuff(obj, target, unit, buffID1, buffID2, buffID3)
    end
end

--[ 造成HP伤害并吸血 ] 伤害百分比，绝对值加成，额外暴击几率，额外命中几率，吸血百分比
function atkAddHp(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, vampire)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    local shangHai = data.shangHai
    if shangHai then
        shangHai=shangHai*vampire
        sendAddHp(obj, obj, shangHai)
    end
end

--[ 造成HP伤害并吸血并增加Buf ] 伤害百分比，绝对值加成，额外暴击几率，额外命中几率，吸血百分比, buffID1, buffID2, buffID3
function atkAddHpMultiBuff(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, vampire, buffID1, buffID2, buffID3)
    atkAddHp(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, vampire)
    if data.isHit then
        addMultiBuff(obj, target, unit, buffID1, buffID2, buffID3)
    end
end

--[链接人]
function linkBuff(obj,target,unit, bufID, effectID)
    Buffer.add(bufID, obj, obj)
    local msg = Msg.gc.GC_BUFFER_LINK
    msg.objid = obj.id
    msg.target = target.id
    msg.effectID = effectID
    Obj.sendArea(msg, obj.scene_id, obj.x, obj.y)
end

-- [添加选定目标Buff]
function multiTargetBuff(obj, target, unit, buffID)
    data.isHit = SkillDefine.HIT_TYPE_OTHER
    if unit.flag then
        if not obj.links then
            obj.links = {}
        end
        obj.links[target.id] = target.uid
        Buffer.add(buffID, target, obj, unit.level)
        return
    end
    if obj.links then
        for k in pairs(obj.links) do
            obj.links[k] = nil
        end
    else
        obj.links = {}
    end
    obj.linkMain = target.id    --主光线
    obj.linkMainUid = target.uid  --主光线
    obj.links[target.id] = target.uid
    Buffer.add(buffID, target, obj, unit.level)
end


-- [ 弹跳伤害 ] 伤害百分比，绝对值加成，额外暴击率，额外命中率，主属性类型，主属性加成比例，弹跳次数，每次跳跃伤害衰减
function atkJump(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, jumpMaxNum, damageAttenuation, radius)
    if unit.flag then
        atk(obj, target, unit, skillHurt*damageAttenuation, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
        return
    end

    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)

    if not radius or radius==0 then
        return
    else
        tempList[0] = 0
        Scan.utilityScan(Scan.AOE_TYPE_ROUND, obj, target, unit, onCallbackScan, conditionFuncScan, jumpMaxNum, radius)
    end

    local oldX      = unit.x
    local oldY      = unit.y
    local oldTX     = unit.tx
    local oldTY     = unit.ty
    local oldTargetID = unit.tid
    local oldTargetUid  = unit.tuid
    local calcFlyTime = Unit.calcFlyTime

    local len = tempList[0]
    local index = 0


    unit.x = target.x
    unit.y = target.y

    -- 排序
    for i=len, 1, -1 do
        local minLen= -1
        local minIndex = 0
        for j=len, index+1, -1 do
            local tempObj = tempList[j]
            local minObj = index==0 and target or tempList[index]
            local distLen = dist(tempObj.x, tempObj.y, minObj.x, minObj.y)
            if (minLen==-1 or distLen<minLen) then
                minLen = distLen
                minIndex = j
            end
        end
        if minIndex>0 then
            index = index+1
            tempList[index], tempList[minIndex] = tempList[minIndex], tempList[index]
            if index >=jumpMaxNum then
                break
            else
                local tempObj     = tempList[index]   --距离最短
                local newUnit     = Unit.clone(unit)
                newUnit.flag      = true
                newUnit.tid       = tempObj.id
                newUnit.tuid      = tempObj.uid
                newUnit.tx, newUnit.ty = tempObj.x, tempObj.y
                newUnit.delay     = calcFlyTime(newUnit)  -- 飞行时间
                newUnit:onStart()

                unit.x = newUnit.tx
                unit.y = newUnit.ty
            end
        end
    end
    unit.x = oldX
    unit.y = oldY
    unit.tx = oldTX
    unit.ty = oldTY
    unit.tid = oldTargetID
    unit.tuid = oldTargetUid
end

-- [扣血]
function atkHp(obj, target, unit, rate, value)
    local max   = Obj.getHpMax(target)
    local shangHai  = max * rate + value
    if shangHai<1 then
        shangHai = 1
    end
    --assert(shangHai>0, "shangHai:"..shangHai.." target.obj_type"..target.obj_type.." max:"..max)
    data.shangHai   = shangHai
    data.isHit    = true
    data.isAtk    = true
end

-- [扣血并吸血]
function atkHpVampire(obj, target, unit, rate, value, vampire)
    atkHp(obj, target, unit, rate, value)
    local shangHai = data.shangHai
    if shangHai>0 then
        sendAddHp(obj, obj, shangHai * vampire) --吸血
    end
end

-- [攻击忽略防御并加多个BUF]
function atkNoDefAddMultiBuff(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, buffID1, buffID2, buffID3)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, true)
    if data.isHit then
        addMultiBuff(obj, target, unit, buffID1, buffID2, buffID3)
    end
end

-- [扣法力]
function atkMp(obj, target, unit, rate, value)
    if target.obj_type ~= Obj.TYPE_HUMAN then
        return
    end

    local max  = Obj.getMpMax(target)
    local mp   = max * rate + value
    sendAddMp(target, -mp)
    data.isHit = SkillDefine.HIT_TYPE_OTHER
end



-- [ 弹跳伤害 ] 伤害百分比，绝对值加成，额外暴击率，额外命中率，主属性类型，主属性加成比例，区域半径, 技能ID, aoe数量上限
function atkCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, radius, skillID, aoeMaxNum)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    targetCallSkill(obj, target, unit, radius, skillID, aoeMaxNum)
end

function onCallBackTargetCallSkill(obj, target, unit)
    local x = data.target.x
    local y = data.target.y
    Skill.add(obj, data.skillID, target.x, target.y, x, y, target, unit.level)
end

function targetCallSkill(obj, target, unit, radius, skillID, aoeMaxNum)
    data.skillID = skillID
    data.target  = target
    Scan.utilityScan(Scan.AOE_TYPE_ROUND, obj, target, unit, onCallBackTargetCallSkill, conditionFuncScan, aoeMaxNum, radius)
end

-- [造成HP伤害，如果杀死敌人调用技能]
function atkDieCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, skillID)
    if not unit.flag then
        atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
        if data.shangHai and data.shangHai>=Obj.getHp(target) then
            Callback.onAttack(obj, target, unit.skillID, unit.utilityConfig, data.isHit, data.isHeavyHit, data, data.isGedang, data.shangHai, false)
            if Obj.getHp(target) < 1 then
                Skill.add(obj, skillID, target.x, target.y, obj.x, obj.y, nil, unit.level, true)
            end
            unit.flag = true
        end

        data.isHit = nil
        data.isAtk = nil
    else
        atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)
    end
end

-- [造成HP伤害并加Buffer，如果杀死敌人调用技能]
function atkBuffDieCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, skillID, bufferID1, bufferID2, bufferID3)
    atkDieCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, skillID)
    if not bufferID1 then return end
    Buffer.add(bufferID1, target, obj, unit.level)

    if not bufferID2 then return end
    Buffer.add(bufferID1, target, obj, unit.level)

    if bufferID3 then return end
    Buffer.add(bufferID1, target, obj, unit.level)
end

-- [造成HP伤害，如果暴击调用技能]
function atkBaojiCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, skillID)
    atk(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition)

    if unit.flag then --只允许调用一次
        return
    end
    
    if data.isHeavyHit then       
        Skill.add(obj, skillID, target.x, target.y, obj.x, obj.y, target, unit.level, true)
        unit.flag = true
    end
end


-- [造成HP伤害并添加Buffer，如果暴击调用技能]
function atkBuffBaojiCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, skillID, bufferID1, bufferID2, bufferID3)

    atkBaojiCallSkill(obj, target, unit, skillHurt, addition, critChance, hitChance, mainAttributes, mainAttributesAddition, skillID)
    if not bufferID1 then return end
    Buffer.add(bufferID1, target, obj, unit.level)

    if not bufferID2 then return end
    Buffer.add(bufferID1, target, obj, unit.level)

    if bufferID3 then return end
    Buffer.add(bufferID1, target, obj, unit.level)
end


-- [格挡扣血]
function geDangAtkHp(obj, target, unit, rate, value)
    local shangHai  = obj.attr[GEDANG] * rate + value
    if shangHai<1 then
        shangHai = 1
    end

    data.shangHai   = shangHai
    data.isHit    = true
    data.isAtk    = true
end


-- [嘲讽怪物]
function attract(obj, target, unit, type)
    if target.obj_type~=Obj.TYPE_MONSTER then return end

    if type and  MonsterExcel[target.monster_id].monster_type ==type then
        return
    end
    if obj.obj_type == Obj.TYPE_AOE then
        target.attacker_id = nil
        target.attacker_uid = nil
        target.attacker_wake_up_time = Timer.now + 2000
        SceneEvent.setBan(target.scene_id)
        _G.map_mgr.monster_clear()
        MonsterAi.on_move(target, obj.x, obj.y)
    else
        target.attacker_id = obj.id
        target.attacker_uid = obj.uid
    end
    --target.lock_target = true
    --Obj.move(target, obj.x, obj.y)
end


-- ************************************* BUF类 ***********************************************
-- [添加BUF]
function buff(obj, target, unit, buffID)
    data.isHit = HIT_TYPE_OTHER
    Buffer.add(buffID, target, obj, unit.level)
end

function callSkill(obj, target, unit, skillID)
    if obj.monster_id and 0 < SkillExcel[skillID].xuli_sec then
        MonsterAi.skill(obj, skillID, target.x, target.y, target)
    else
        if not target then
            return
        end
        Skill.add(obj, skillID, target.x, target.y, obj.x, obj.y, target, unit.level, true)
    end
end


-- ************************************* BUF类 ***********************************************
-- [添加BUF]
function buff(obj, target, unit, buffID)
    Buffer.add(buffID, target, obj, unit.level)
end

-- [添加多个BUF]
function addMultiBuff(obj, target, unit, buffID1, buffID2, buffID3, buffID4, buffID5)
    if not buffID1 then return end
    Buffer.add(buffID1, target, obj, unit.level)

    if not buffID2 then return end
    Buffer.add(buffID2, target, obj, unit.level)

    if not buffID3 then return end
    Buffer.add(buffID3, target, obj, unit.level)

    if not buffID4 then return end
    Buffer.add(buffID4, target, obj, unit.level)

    if not buffID5 then return end
    Buffer.add(buffID5, target, obj, unit.level)
end

-- [清除BUF作用]
function clearBuf(obj, target, unit, isGood, num)
    Buffer.clearBuf(target, isGood, num)
    Skill.isHit = SkillDefine.HIT_TYPE_OTHER
end


-- [删除BUF作用]
function clearBuffUtility(obj, target, unit, cmd, isGood, num)
    Buffer.clearBuffUtility(target, cmd, isGood, num)
end

-- [移除Buff组]
function clearBuffGroup(obj, target, unit, groupID1, groupID2, groupID3, groupID4, groupID5)
    Buffer.clearBuffGroup(target, groupID1)

    if not groupID2 then return end
    Buffer.clearBuffGroup(target, groupID2)

    if not groupID3 then return end
    Buffer.clearBuffGroup(target, groupID3)

    if not groupID4 then return end
    Buffer.clearBuffGroup(target, groupID4)

    if not groupID5 then return end
    Buffer.clearBuffGroup(target, groupID5)
end

-- [进入状态并且添加Buffer]
function enterStatusAddBuf(obj, target, unit, status, bufferID)
    if status>0 and status <=Status.MAX_HUMAN_STATUS_NUM then
        Status.doHumanControlStatusChange(target, status, "", bufferID, target, unit.level, unit.tx, unit.ty)
        if status == Status.HUMAN_STATUS_SING then
            local mm = Msg.gc.GC_DU_TIAO
            mm.obj_id = target.id
            mm.msec = ExcelBuffer.Buff[bufferID].time * 1000
            target.du_tiao_end_time = mm.msec + Timer.now
            Obj.sendArea(mm, target.scene_id, target.x, target.y)
        elseif status == Status.HUMAN_STATUS_GUIDE and obj.obj_type == Obj.TYPE_HUMAN then
            local mm = Msg.gc.GC_SKILL_GUIDE_DU_TIAO
            mm.msec = ExcelBuffer.Buff[bufferID].time * 1000
            mm.desc = SkillExcel[unit.skillID].name
            Msg.send(mm, obj.fd)       
        end
    else
        assert(nil, "enterStatusAddBuf"..status)
    end

    obj.shikongstep = 2
end

-- [复制目标添加BUF 将目标的（攻击力、形象等)变成你自己的]	参数 buffID
function copyAddBuf(obj, target, unit, buffID)
    Buffer.add(buffID, obj, target, unit.level)
end

-- [增加Buff叠加次数]
function addBufCount(obj, target, unit, bufferID, num)
    Buffer.addBufCount(target, bufferID, num)
end
-- [减少Buff叠加次数]
function delBufCount(obj, target, unit, bufferID, num)
    Buffer.delBufCount(target, bufferID, num)
    
end

function randBuf(obj, target, unit, bufids)
    local buffID = bufids[math.random(1, #bufids)]
    Buffer.add(buffID, target, obj, unit.level)
end


-- ************************************* ATK类 ***********************************************







-- ************************************* 调用技能类 ***********************************************

function callSkillOwner(obj, target, unit, skillID)
    local owner = Obj.getOwner(obj)
    if owner then
        callSkill(obj, owner, unit, skillID)
    end
end




--[解除技能冷却]
function clearCD(obj, target, unit, skillGroupID)
    local userData      = Skill.getUserData(target)
    local coolDownList  = userData.cooldown
    local flag
    for i = 0, userData.cooldownLen - 1 do
        local skill = SkillExcel[coolDownList[i].id]
        if skill and skill.group == skillGroupID then
            coolDownList[i].time = Timer.now - skill.coolDown
            coolDownList[i].count = skill.coolDownCount
            flag = true
        end
    end

    if flag and target.fd then
        local mm = Msg.gc.GC_SKILL_COOLDOWN
        mm.skillGroupID = skillGroupID
        mm.type = SkillDefine.COOLDOWN_SKILL_CLEAR_ALL
        mm.time = 0
        mm.rate = 0
        Msg.send(mm, target.fd)
    end

end

--[降低技能冷却] 技能ID，降低冷却时间（单位：毫秒）
function reduceCD(obj, target, unit, skillGroupID, time)
    local userData = Skill.getUserData(target)
    local coolDownList = userData.cooldown
    local flag
    for i = 0, userData.cooldownLen - 1 do
        local skill = SkillExcel[coolDownList[i].id]
        if skill and skill.group == skillGroupID then
            coolDownList[i].time = math.max(coolDownList[i].time-time, 0)
            flag = true
        end
    end

    if flag and target.fd then
        local mm = Msg.gc.GC_SKILL_COOLDOWN
        mm.skillGroupID = skillGroupID
        mm.type = SkillDefine.COOLDOWN_SKILL_DEC
        mm.time = time
        mm.rate = 0
        Msg.send(mm, target.fd)
    end

end

--[降低技能冷却] 降低冷却时间（单位：毫秒）, 降低冷却百分比
function reduceAllCD(obj, target, unit, time, rate)
    local userData = Skill.getUserData(target)
    local coolDownList = userData.cooldown
    local flag
    for i = 0, userData.cooldownLen - 1 do
        local skill = SkillExcel[coolDownList[i].id]
        if skill then
            local decTime = time
            if rate and rate > 0 then
                decTime = decTime + skill.coolDown * rate
            end
            coolDownList[i].time = math.max(coolDownList[i].time-decTime, 0)
            flag = true
        end
    end

    if flag and target.fd then
        local mm = Msg.gc.GC_SKILL_COOLDOWN
        mm.skillGroupID = 0
        mm.type = SkillDefine.COOLDOWN_SKILL_ALL_DEC
        mm.time = time
        mm.rate = rate or 0
        Msg.send(mm, target.fd)
    end

end


-- ************************************* 召唤类 ***********************************************

--[ 召唤 ] 召唤单位ID，召唤数量，上限数量，持续时间，攻击继承比例，防御技能比例，体力继承比例
function callMonster(obj, target, unit, monsterID, num, maxNum, time, atkRate, defRate, qixueRate)

    local tx
    local ty
    local utilityConfig = UtilityExcel[unit.utilityID]

    if utilityConfig.acttackCenter == SkillDefine.ACTTACK_TYPE_SELF then
        tx = unit.x
        ty = unit.y
    elseif utilityConfig.acttackCenter == SkillDefine.ACTTACK_TYPE_LOCK_TARGET then
        tx = target.x
        ty = target.y
    else
        tx = unit.tx
        ty = unit.ty
    end

    local attr = obj.attr
    for i=1, num do
        local monster = ObjMonster.create(monsterID)
        local owner = Obj.getOwner(obj) or obj
        monster.owner = {id = owner.id, uid = owner.uid, attr_from_self = true, die_when_owner_die = true}
        monster.dieTime = Timer.now + time * 1000
        monster.camps = owner.camps
        monster.init_hp_max = Obj.getAttr(obj, RoleDefine.HP_MAX)*qixueRate
        monster.init_atk = attr[RoleDefine.ATK]*atkRate
        monster.init_def = attr[RoleDefine.DEF]*defRate
        Obj.doCalc(monster)
        Obj.setHp(monster, Obj.getHpMax(monster))

        ObjMonster.enterScene(monster, unit.sceneID, tx, ty)
        onAddSub(obj, unit.skillID, unit.utilityID, maxNum, monster)
    end
end

-- [ 分身 ]
function clone(obj, target, unit, num, maxNum, monsterID, time, atkRate, defRate, qixueRate)

    local cur = random() * math.pi * 2
    local per = math.pi * 2 / num
    local r = 100
    local attr = obj.attr
    for i=1, num do
        local monster = ObjMonster.clone(monsterID, obj)
        monster.owner = {id = obj.id, uid = obj.uid, attr_from_self = true, die_when_owner_die = true}
        monster.dieTime = Timer.now + time * 1000
        monster.init_hp_max = Obj.getAttr(obj, RoleDefine.HP_MAX)*qixueRate
        monster.init_atk = attr[RoleDefine.ATK]*atkRate
        monster.init_def = attr[RoleDefine.DEF]*defRate
        Obj.doCalc(monster)
        Obj.setHp(monster, Obj.getHpMax(monster))
        ObjMonster.enterScene(monster, unit.sceneID, unit.tx + r*math.cos(cur), unit.ty + r*math.sin(cur))
        cur = cur + per
        onAddSub(obj, unit.skillID, unit.utilityID, maxNum, monster)
    end
end

--[ 放置OBJ ]	形象ID，脚底ID, 释放技能ID，释放次数，释放间隔，攻击方式(0：鼠标点放置、1：移动至鼠标点、2：跟踪、3：顺时针 4：逆时针)，移动速度，Obj生存时间(毫秒)，移动最大距离, 特效组合类型(0:无，1：线性组合[参数：宽, 高])
function addObj(obj, target, unit, bodyID, bottomID, skill_id, maxCount, aoeSpeed, attackType, speed, lifeTime, len, comboType, comboArg1, comboArg2)
    local tx
    local ty
    local utilityConfig = UtilityExcel[unit.utilityID]

    if utilityConfig.acttackCenter == SkillDefine.ACTTACK_TYPE_SELF then
        tx = unit.x
        ty = unit.y
    elseif target and utilityConfig.acttackCenter == SkillDefine.ACTTACK_TYPE_LOCK_TARGET then
        tx = target.x
        ty = target.y
    else
        tx = unit.tx
        ty = unit.ty
    end


    local aoe = ObjAoe.create(obj, bodyID, bottomID, skill_id, unit.level, maxCount, aoeSpeed, unit.dir, comboType, comboArg1, comboArg2)
    if not aoe then
        return
    end
    aoe.dieTime = Timer.now+lifeTime
    aoe.attackType = attackType
    aoe.pathLocus = nil
    aoe.targetID = nil

    if attackType == 1 or attackType == 5 then	--移动至目标点
        aoe.speed = speed
        Obj.enterScene(aoe, unit.sceneID, unit.x, unit.y)
        local x, y = unit.tx, unit.ty
        if attackType == 1 then
            SceneEvent.setBan(unit.sceneID)
            x, y = Map.through(unit.sceneID, unit.x, unit.y, unit.tx, unit.ty)
        end
        Obj.move(aoe, x, y)
    elseif attackType == 2 or attackType == 6 then	--移动目标方向
        aoe.speed = speed
        Obj.enterScene(aoe, unit.sceneID, unit.x, unit.y)
        local x, y = Util.getAnglePoint2(unit.x, unit.y, tx, ty, len)
        if attackType == 2 then
            SceneEvent.setBan(unit.sceneID)
            x, y = Map.through(unit.sceneID, unit.x, unit.y, x, y)
        end
        Obj.move(aoe, x, y, nil, true)
    elseif attackType == 3 or attackType == 4 then	--顺时针 --逆时针
        aoe.speed = speed
        Obj.enterScene(aoe, unit.sceneID, tx, ty, true)
        aoe.path = aoe.path or {}
        aoe.path_len = Util.setRoundPath(aoe.path, unit.x, unit.y, tx, ty, attackType == 3)
        aoe.pathLocus = aoe.pathLocus or {}
        aoe.pathLenLocus = aoe.path_len
        for i=1, aoe.path_len do
            aoe.pathLocus[i] = aoe.path[i]
        end

        Obj.movePath(aoe)
    elseif attackType == 7 then

    else
        Obj.enterScene(aoe, unit.sceneID, tx, ty)
    end
end

--[ 放置魔法 ]	特效ID，释放技能ID，攻击力, 释放间隔，攻击方式(0：鼠标点放置、1：移动至鼠标点, 2:移动鼠标方向)，移动速度，Obj生存时间(毫秒)，移动最大距离
function addMagic(obj, target, unit, bodyID, bottomID, skill_id, atk, aoeSpeed, attackType, speed, lifeTime, len)

    local tx
    local ty
    local utilityConfig = UtilityExcel[unit.utilityID]

    if utilityConfig.acttackCenter == SkillDefine.ACTTACK_TYPE_SELF then
        tx = unit.x
        ty = unit.y
    elseif utilityConfig.acttackCenter == SkillDefine.ACTTACK_TYPE_LOCK_TARGET then
        tx = target.x
        ty = target.y
    else
        tx = unit.tx
        ty = unit.ty
    end

    local magic = ObjMagic.create(bodyID, bottomID, skill_id, unit.level, atk, aoeSpeed)
    magic.dieTime = Timer.now+lifeTime

    if obj.obj_type == Obj.TYPE_MONSTER then
        magic.camps = obj.camps
    end

    if attackType == 1 then	--移动至目标点
        magic.speed = speed
        Obj.enterScene(magic, unit.sceneID, unit.x, unit.y, true)
        Obj.move(magic, tx, ty, nil, true)
    elseif attackType == 2 then	--移动目标方向
        magic.speed = speed
        Obj.enterScene(magic, unit.sceneID, unit.x, unit.y, true)
        local x, y = Util.getAnglePoint2(unit.x, unit.y, tx, ty, len)
        Obj.move(magic, x, y, nil, true)
    else
        Obj.enterScene(magic, unit.sceneID, tx, ty, true)
    end
end

--[ 添加陷阱 ]	特效ID，触发时技能ID，数量上限，攻击方式(0：鼠标点放置、1：移动至鼠标点)，移动速度，Obj生存时间(毫秒)，敌友条件，触发半径
function addTrap(obj, target, unit, bodyID, bottomID, skill_id, maxNum, lifeTime, friendAndFoeConditions, radius)
    local trap = ObjTrap.create(obj, bodyID, bottomID, skill_id, unit.level, friendAndFoeConditions, radius)
    trap.dieTime = Timer.now+lifeTime
    Obj.enterScene(trap, unit.sceneID, unit.tx, unit.ty)
    onAddSub(obj, unit.skillID, unit.utilityID, maxNum, trap)
end



-- ************************************* 位移类 ***********************************************




-- [拉近目标]
function pull(obj, target, unit)
    local dx = target.x - unit.x
    local dy = target.y - unit.y
    local radius = Obj.getRadius(target)+ Obj.getRadius(obj)
    if dx * dx + dy * dy < radius * radius then
        return
    end
    if target.monster_id then
        if ObjMonster.getValueByMonsterType(target, "can_hit_back_fly") == 0 then
            return
        end
    end
    local x, y = Util.getTargetPoint(target.x, target.y, unit.x, unit.y, radius)
    SceneEvent.setBan(target.scene_id)
    x, y = Map.through(target.scene_id, target.x, target.y, x, y)

    Notify.GC_SKILL_ACTION(obj, unit.sceneID, unit.skillID, SkillExcel[unit.skillID], unit.utilityConfig, x, y, unit.x, unit.y, target)
    local dir =  Obj.getDir(x-target.x, y-target.y)
    target.dir = dir
    Obj.jump(target, x, y)

    if target.monster_id then
        target.wake_up_time = 1000 + Timer.now
    end

end


-- ************************************* 其 他 *************************************************

-- [即死]
function kill(obj, target, unit)
    if target.obj_type==Obj.TYPE_MONSTER then
        local shangHai = Obj.getHp(target)
        Obj.setHp(target, 0)
        Callback.onDie(target, obj, unit.utilityConfig, shangHai)        
        if MonsterExcel[target.monster_id].die_remain <= 0 then
            ObjMonster.destroy(target)
        end
    elseif target.obj_type==Obj.TYPE_AOE then
        ObjAoe.destroy(target)
    elseif target.obj_type==Obj.TYPE_TRAP then
        ObjTrap.destroy(target)
    elseif target.obj_type~=Obj.TYPE_PET then
        assert(nil, target.obj_type)
    end


end

function goHome(obj)--, target)
    if obj.obj_type == Obj.TYPE_HUMAN then
        local map_id = Map.scene_id_to_map_id[obj.scene_id]
        local cf = ExcelMapConf.scene[map_id].revive
        if cf[1] and cf[1] == 6 then
            obj.db.reviveNowItemNum = 0
        end
    end
    RoleRevive.goHome(obj)
end

function onHorse(obj)--, target)
    local status = obj.status
    if status == Status.HUMAN_STATUS_TALISMAN and obj.db.chooseHorseId > 0 then
        obj.db.isMount = 1
        Status.doHumanControlStatusChange(obj, Status.HUMAN_STATUS_NORMAL)
        ObjHuman.doCalc(obj)
        HorseLogic.sendHorseStatus(obj)
        SkillNotify.GC_SKILL_LIST(obj)
        SkillNotify.GC_SKILL_LIST_CHANGE(obj)
        SkillNotify.GC_SKILL_MAIN_MENU(obj)
    else
        Status.doHumanControlStatusChange(obj, Status.HUMAN_STATUS_NORMAL)

    end

end

function addPetSkill(obj, target, unit, skillID)
    if not obj.fight_pet_id then return end
    local pet = Obj.getObj(obj.fight_pet_id, obj.fight_pet_uid)
    if not pet then return end

    Skill.add(pet, skillID, pet.x, pet.y, pet.id, pet.x, pet.y, unit.level, true)
end
